﻿#pragma once

#include "Misc/CommonMacros.h"
#include "Containers/Containers.h"
#include "AssetPath.h"
#include "AssetHead.h"
#include "Framework/ApplicationSubsystem.h"
#include "Async/PackagedTask.h"
#include "Path/Path.h"
#include "AssetPackage.h"

#include "AssetSubsystem.m.h"

class CAssetReader;
class CAssetWriter;
class RAssetObject;
class RTexture2D;

struct SAssetLoadResult
{
    SAssetHead Head;
    RObjectPtr MainObject;
    RObjectPtr EditInfoObject;
    TSharedObjectPtr<RTexture2D> Thumbnail;
};

struct SImportResult
{
    SAssetPath AssetPath;
    SAssetHead Head;
};

RCLASS(ExtraHeader="Texture/Texture2D.h")
class WHENGINE_API RAssetSubsystem : public RApplicationSubsystem
{
    ROBJECT
protected:
    void Initialize() override;
    void Deinitialize() override;

public:
    static RAssetSubsystem* Get();
    
    RFUNCTION()
    static RAssetSubsystemPtr GetInstance();

    CAssetReader* GetAssetReader() const { return AssetReader; }
    CAssetWriter* GetAssetWriter() const { return AssetWriter; }

    //add a package to asset subsystem
    //can run in any thread
    void AddPackage(const SAssetPackage& InPackage);

    //get all found packages
    //can run in any thread
    TVector<SAssetPackage> GetAllPackages();
    
    //find a package by name
    //can run in any thread
    SAssetPackage FindPackage(const CString& InPackageName);

    //SaveAsset
    //only can invoke in main thread
    void AsyncSaveAsset(const SAssetPath&  AssetPath
        ,RObjectPtr AssetMainObject
        ,RObjectPtr EditInfoObject
        ,TSharedObjectPtr<RTexture2D> Thumbnail=nullptr
        ,const TDelegate<void,SAssetHead>& OnSaveCompleted=nullptr
        ,TSharedObjectPtr<RAssetObject> AssetObject=nullptr // if do not have asset object, will create a new one
        ,bool bBlockUntilFinished=false //if true, will block main thread until save completed
        );

    //load Asset    
    //can invoke in ANY thread
    //@ SectionsToLoad , which section to load,  always load head section
    //@ OnLoaded, callback when load completed, only invoke in main thread
    void AsyncLoadAsset(
        const SAssetPath&  AssetPath
        ,EAssetSection  SectionsToLoad
        ,TDelegate<void,SAssetLoadResult> OnLoaded=nullptr
        ,TDelegate<void> OnLoadFailed=nullptr
    );

    void AsyncLoadAsset(
        const SAssetPath&  AssetPath
        ,const TVector<uint8_t>& AssetFileData
        ,EAssetSection  SectionsToLoad
        ,TDelegate<void,SAssetLoadResult> OnLoaded=nullptr
        ,TDelegate<void> OnLoadFailed=nullptr
    );
    
    //load asset in main thread
    //will block main thread until load completed
    RFUNCTION()
    static TSharedObjectPtr<RObject> LoadAsset(
        const SAssetPath& InAssetPath);

    //在当前线程获取一个文件的AssetPath
    //如果文件不存在或者文件内容为空，返回空
    TOptional<SAssetPath> LoadAssetPath(CString PackageName,const SPath& AssetFilePath);

    //在当前线程获取一个资产文件的头部
    //如果文件不存在或者文件内容为空，返回空
    TOptional<SAssetHead> LoadAssetHead(const SPath& AssetFilePath);

    //从资产文件路径创建资产路径
    SAssetPath CreateAssetPath(
        CClass*  InAssetType
        ,const CString& InRegisteredPackageName
        , const SPath& AssetFilePath
    );

    static CString GetRelativePathToPackage(const SPath& InPackagePath,const SPath& InAssetPath);

    //从内存中加载一个资产文件的主要对象
    RFUNCTION()
    static TSharedObjectPtr<RObject> LoadAsset(
        const TVector<uint8_t> & InAssetFileData
        ,const SAssetPackage & InPackage
        ,const CString & InAssetRelativePathToPackage
        );

    /// @brief 从内存中加载一个资产文件的主要对象和编辑信息对象
    /// @param InAssetFileData 
    /// @param InPackage 
    /// @param InAssetRelativePathToPackage 
    /// @return  first object is main object, second object is edit info object
    static TTuple<TSharedObjectPtr<RObject>,TSharedObjectPtr<RObject>> LoadAssetWithEditInfo(
        const TVector<uint8_t> & InAssetFileData
        ,const SAssetPackage & InPackage
        ,const CString & InAssetRelativePathToPackage
        );

    //在主线程加载一个资产文件
    static TOptional<SAssetLoadResult> LoadAsset(
        const SAssetPath&  AssetPath
        ,EAssetSection  SectionsToLoad
    );

    //在主线程加载一个资产文件, 从内存中加载
    static TOptional<SAssetLoadResult> LoadAssetWithFileData(
        const TVector<uint8_t>& InAssetFileData
        ,const SAssetPath&  AssetPath
        ,EAssetSection  SectionsToLoad
    );

    //resave a loaded asset,  typicallly used for save edited asset
    void AsyncResaveLoadedAsset(
        RObjectPtr AssetMainObject
        ,RObjectPtr EditInfoObject
        ,TSharedObjectPtr<RTexture2D> Thumbnail=nullptr
        ,const TDelegate<void,SAssetHead>& OnSaveCompleted=nullptr
    );

    SAssetHead ResaveLoadedAsset(
        RObjectPtr AssetMainObject
        ,RObjectPtr EditInfoObject
        ,TSharedObjectPtr<RTexture2D> Thumbnail=nullptr
    );

    
    //create asset in a folder for a exist object 
    //assume donot have a asset path now,will create a new one 
    void AsyncImportObject(RObjectPtr InObject, 
        RObjectPtr InEditInfoObject,
        TSharedObjectPtr<RTexture2D> Thumbnail,
        const CString & InPackageName,
        const CString& InQueryFolderRelativePath,
        const TDelegate<void, TOptional<CString> /*ErrorMessage*/, SAssetPath,SAssetHead>& OnFinished = nullptr
        );

    //create asset in a folder for a exist object 
    //assume donot have a asset path now,will create a new one 
    //will block main thread until import completed
    SImportResult ImportObject(RObjectPtr InObject, 
        RObjectPtr InEditInfoObject,
        TSharedObjectPtr<RTexture2D> Thumbnail,
        const CString & InPackageName,
        const CString& InQueryFolderRelativePath
    );



    //can run in any thread
    SAssetHead AnyThreadParseAssetHead(const TVector<uint8_t>& InAssetFileData/* only head part is needed */);


    //can run in any thread
    SAssetHead AnyThreadParseAssetHeadFromJson(const CString& HeadJson,uint64_t InHeadSize);
    static TVector<uint8_t> AnyThreadSaveAssetHead(const SAssetHead& InHead);
    
    //替换掉文件头部
    //文件头部大小会重新计算
    //返回新的头部大小
    static uint64_t ReplaceHead(
        TVector<uint8_t> & InOutAssetFileData
        ,uint64_t InOldHeadSize
        ,const SAssetHead & InOutNewHead
    );
protected:
    CAssetReader* AssetReader = nullptr;
    CAssetWriter* AssetWriter = nullptr;


    //all found packages, packages will be found by AssetReader
    std::mutex PackagesLock;
    TMap<CString,SAssetPackage> Packages;

    //paser asset with All nessary data
    //can run in any thread
    SAssetLoadResult ParseAsset(
        const TVector<uint8_t>& InAssetFileData
        , EAssetSection SectionsToLoad
        , const SAssetPath& AssetPath
        , const SAssetHead& Head
        , TSharedObjectPtr<RAssetObject> AssetObject
        );

    //递归地找到一个资产文件的依赖资产文件
    void AnyThreadRecursiveFindDependencies(
        const SAssetHead& InHead
        ,TVector<SAssetPath>& OutDependencies
        ,TVector<SAssetHead>& OutDependencyHeads
    );

};
